import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;

import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.control.IndexRange;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;

//@author A0096724W
public class GUIController {

    /**********************
     * Private Variables
     **********************/
    
    //Logic Components to carry out operations
    private InputParser inputParser;
    private Functions functions = new Functions();
    
    //GUI Components
    private TextField textField;
    private TextArea textArea;
    
    private ListView<Task> eventList;
    private ListView<Task> deadlineList;
    private ListView<Task> miscList;
    private static Label eventStatus;
    private static Label deadlineStatus;
    private static Label miscStatus;
    
    //Flag to toggle autoSuggest on and off
    private Label autoSuggestStatus;
    private boolean isAutoSuggest = true;
    private AddSuggest suggester;
    
    //Dictionary for autoComplete
    private ArrayList<String> words;
    
    //Key combination to toggle between textField and lists (CTRL + TAB)
    final KeyCombination kb = new KeyCodeCombination(KeyCode.TAB, KeyCombination.CONTROL_DOWN);

    GUIController(TextField tf, TextArea ta, final ListView<Task> eventList, 
	    final ListView<Task> deadlineList, final ListView<Task> miscList, 
	    Label eventStatus, Label deadlineStatus, Label miscStatus, Label autoSuggestStatus) {
	
	initaliseVariables(tf, ta, eventList, deadlineList, miscList,
		eventStatus, deadlineStatus, miscStatus, autoSuggestStatus);

	setTextFieldBehaviour(eventList, deadlineList, miscList);
	setEventListTogglingBehaviour(eventList, deadlineList, miscList);
	setDeadlineListTogglingBehaviour(eventList, deadlineList, miscList);
	setMiscListTogglingBehaviour(eventList, deadlineList, miscList);

	initDictionaryForAutoComplete();

    }

    private void initDictionaryForAutoComplete() {
	words = new ArrayList<String>();
	words.add("add");
	words.add("display");
	words.add("delete");
	words.add("update");
	words.add("undo");
	words.add("exit");
	words.add("search");
	words.add("from");
	words.add("due by");
	words.add("urgent");
	words.add("offurgent");
	words.add("reminder");
	words.add("offreminder");
	words.add("events");
	words.add("deadlines");
	words.add("misc");
	words.add("calendar");
	words.add("exit");
	words.add("done");

	Collections.sort(words);
    }

    private void setMiscListTogglingBehaviour(final ListView<Task> eventList,
	    final ListView<Task> deadlineList, final ListView<Task> miscList) {
	miscList.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
	    @Override
	    public void handle(KeyEvent event) {
		if (event.getCode().equals(KeyCode.LEFT)) {
		    goToLeftList(eventList, deadlineList, event);
		}

		if (event.getCode().equals(KeyCode.DELETE)) {
		    deleteSelectedMiscItem(miscList);
		}

		if (kb.match(event)) {
		    focusTextField(event);
		}

	    }

	    private void deleteSelectedMiscItem(final ListView<Task> miscList) {
		SelectionModel<Task> model = miscList.getSelectionModel();
		if (!model.isEmpty()) {
		    int index = model.getSelectedIndex() + 1;
		    try {
			ArrayList<Integer> toDelete = new ArrayList<Integer>();
			toDelete.add(index);
			Feedback feedback = Functions.deleteAll("misc", toDelete);
			showFeedback(feedback);
		    } catch (Exception e) {
			e.printStackTrace();
		    }
		}
	    }

	    private void goToLeftList(final ListView<Task> eventList,
		    final ListView<Task> deadlineList, KeyEvent event) {
		if (deadlineList.getItems().isEmpty()) {
		    if (!eventList.getItems().isEmpty()) {
			goToList(eventList);
		    }
		} else {
		    goToList(deadlineList);
		}
		event.consume();
	    }

	});
    }
    
    private void goToList(final ListView<Task> eventList) {
	eventList.requestFocus();
	eventList.getSelectionModel().select(0);
	eventList.scrollTo(0);
    }
    
    private void focusTextField(KeyEvent event) {
	textField.requestFocus();
	event.consume();
    }

    private void setDeadlineListTogglingBehaviour(
	    final ListView<Task> eventList, final ListView<Task> deadlineList,
	    final ListView<Task> miscList) {
	deadlineList.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
	    @Override
	    public void handle(KeyEvent event) {
		if (event.getCode().equals(KeyCode.LEFT)) {
		    if (!eventList.getItems().isEmpty()) {
			goToList(eventList);
		    }
		    event.consume();
		}

		if (event.getCode().equals(KeyCode.RIGHT)) {
		    if (!miscList.getItems().isEmpty()) {
			goToList(miscList);
		    }
		    event.consume();
		}

		if (event.getCode().equals(KeyCode.DELETE)) {
		    deleteSelectedDeadlineItem(deadlineList);
		}

		if (kb.match(event)) {
		    focusTextField(event);
		}
	    }

	    private void deleteSelectedDeadlineItem(
		    final ListView<Task> deadlineList) {
		SelectionModel<Task> model = deadlineList.getSelectionModel();
		if (!model.isEmpty()) {
		int index = model.getSelectedIndex() + 1;
		try {
		    ArrayList<Integer> toDelete = new ArrayList<Integer>();
		    toDelete.add(index);
		    Feedback feedback = Functions.deleteAll("deadline", toDelete);
		    showFeedback(feedback);
		} catch (Exception e) {
		    e.printStackTrace();
		}
		}
	    }
	});
    }

    private void setEventListTogglingBehaviour(final ListView<Task> eventList,
	    final ListView<Task> deadlineList, final ListView<Task> miscList) {
	eventList.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
	    @Override
	    public void handle(KeyEvent event) {
		if (event.getCode().equals(KeyCode.RIGHT)) {

		    if (deadlineList.getItems().isEmpty()) {
			if (!miscList.getItems().isEmpty()) {
			    goToList(miscList);
			}
		    } else {
			goToList(deadlineList);
		    }

		    event.consume();
		}

		if (event.getCode().equals(KeyCode.DELETE)) {
		    deleteSelectedEventItem(eventList);
		}

		if (kb.match(event)) {
		    focusTextField(event);
		}

	    }

	    private void deleteSelectedEventItem(final ListView<Task> eventList) {
		SelectionModel<Task> model = eventList.getSelectionModel();
		if (!model.isEmpty()) {
		int index = model.getSelectedIndex() + 1;
		try {
		    ArrayList<Integer> toDelete = new ArrayList<Integer>();
		    toDelete.add(index);
		    Feedback feedback = Functions.deleteAll("event", toDelete);
		    showFeedback(feedback);
		} catch (Exception e) {
		    e.printStackTrace();
		}
		}
	    }
	});
    }

    private void setTextFieldBehaviour(final ListView<Task> eventList,
	    final ListView<Task> deadlineList, final ListView<Task> miscList) {
	textField.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
	    @Override
	    public void handle(KeyEvent event) {
		String currentText = textField.getText();
		int caretPos = textField.getCaretPosition();

		if (kb.match(event)) {
		    goToListWithSelected(eventList, deadlineList, miscList,
			    event);
		} else if (event.getCode() == KeyCode.TAB) {
		    toggleSuggested(event, currentText, caretPos);
		}                 
	    }

	    private void toggleSuggested(KeyEvent event, String currentText,
		    int caretPos) {
		try {
		if (currentText.substring(caretPos - 10, caretPos).equals(" due date ")) {
		    textField.replaceText(caretPos - 19, caretPos + 1, "");
		    textField.end();

		} else if (currentText.substring(caretPos - 12, caretPos).equals(" start time ")) {
		    textField.replaceText(caretPos - 19, caretPos + 1," due by \\ due date \\");
		    textField.selectRange(caretPos - 10, caretPos);
		}

		} catch (Exception e) {

		}
		event.consume();
	    }

	    private void goToListWithSelected(final ListView<Task> eventList,
		    final ListView<Task> deadlineList,
		    final ListView<Task> miscList, KeyEvent event) {
		if (!eventList.getSelectionModel().isEmpty()) {
		    eventList.requestFocus();
		} else if (!deadlineList.getSelectionModel().isEmpty()) {
		    deadlineList.requestFocus();
		} else if (!miscList.getSelectionModel().isEmpty()) {
		    miscList.requestFocus();
		} else {
		    eventList.requestFocus();
		    if (!eventList.getItems().isEmpty()) {
			eventList.scrollTo(0);
			eventList.getSelectionModel().select(0);
		    }
		}
		event.consume();
	    }
	});
    }

    private void initaliseVariables(TextField tf, TextArea ta,
	    final ListView<Task> eventList, final ListView<Task> deadlineList,
	    final ListView<Task> miscList, Label eventStatus,
	    Label deadlineStatus, Label miscStatus, Label autoSuggestStatus) {
	textField = tf;
	textArea = ta;
	this.eventList = eventList;
	this.deadlineList = deadlineList;
	this.miscList = miscList;
	this.eventStatus = eventStatus;
	this.deadlineStatus = deadlineStatus;
	this.miscStatus = miscStatus;
	this.autoSuggestStatus = autoSuggestStatus;
	suggester = new AddSuggest();
    }
    
    //Control GUI response based on Key stroke entered by user
    public void invokeBehaviour(KeyEvent e) {

	if (isNotDeletionOrEnter(e)) {
	    autoComplete();
	}

	if (isF2(e)) {
	    toggleAutoSuggest();
	}

	if (isAutoSuggest) {
	    executeAutoSuggest(e);
	}

	if (isEnter(e)) {
	    executeCommand();
	}
    }

    private boolean isEnter(KeyEvent e) {
	return e.getCode().equals(KeyCode.ENTER);
    }

    private void executeCommand() {
	try {
	    functions.setListStatus(eventStatus.getText(), deadlineStatus.getText(), miscStatus.getText());
	    Feedback feedback = parseAndExecuteCommand();
	    resetTextField(textField);
	    showFeedback(feedback);
	} catch (Exception ex) {
	    updateTextArea(ex.getMessage() + "\n");
	}
    }

    private Feedback parseAndExecuteCommand() throws Exception {
	String input = textField.getText();
	initInputParser(input);
	Feedback feedback = inputParser.parse(input, functions);
	return feedback;
    }

    private void executeAutoSuggest(KeyEvent e) {
	if (e.getCode().equals(KeyCode.SPACE)) {
	suggester.suggestNext(textField.getText(), textField.getCaretPosition(), textField);
	}
    }

    private void toggleAutoSuggest() {
	isAutoSuggest = !isAutoSuggest;

	if (isAutoSuggest) {
	autoSuggestStatus.setText("ON");
	} else {
	autoSuggestStatus.setText("OFF");
	}
    }

    private boolean isF2(KeyEvent e) {
	return e.getCode().equals(KeyCode.F2);
    }
    
    private boolean isNotDeletionOrEnter(KeyEvent e) {
	
	return !isEnter(e) &&          //Do not autoComplete when user wants to confirm current input as cmd
		!e.getCode().equals(KeyCode.BACK_SPACE) &&    //Do not autoComplete when user does not want the autoComplete
		!e.getCode().equals(KeyCode.DELETE);
    }

    private void showFeedback(Feedback feedback) {
	printToTextArea(feedback);
	scrollToAndSelectItem(feedback);
	updateAllHeaders(feedback);
    }

    private void updateAllHeaders(Feedback feedback) {
	String[] listHeader = feedback.getHeaderList();
	updateListStatus(listHeader);
    }

    private void scrollToAndSelectItem(Feedback feedback) {
	int index = feedback.getIndex();
	String listType = feedback.getType();
	scrollToItem(index, listType);
    }

    private void printToTextArea(Feedback feedback) {
	String response = feedback.getFeedback();
	updateTextArea(response);
    }

    private void updateTextArea(String response) {
	String oldText = textArea.getText();
	String timeStamp = "";
	if (!response.equals("")) {
	    timeStamp = createTimeStamp();
	}
	String appendToOld = timeStamp + response + oldText;
	textArea.setText(appendToOld);
    }

    private void initInputParser(String input) {
	inputParser = new InputParser(input);
    }

    private void resetTextField(TextField textField){
	textField.setText("");
    }

    private void scrollToItem(int index, String type) {
	ListView<Task> list = null;
	if (type != null) {
	    list = typeToList(type);
	}

	final ListView<Task> toSelectFrom = list;
	final int indexToSelect = index -1;

	if (list != null && index > 0) {
	    Platform.runLater(new Runnable() {

		@Override
		public void run() {
		    toSelectFrom.scrollTo(indexToSelect);
		    toSelectFrom.getSelectionModel().select(indexToSelect);
		}

	    });
	}

    }

    private ListView<Task> typeToList(String type) {
	if (type.equals("event")) {
	    return eventList;
	}

	if (type.equals("deadline")) {
	    return deadlineList;
	}

	if (type.equals("misc") || type.equals("misc")) {
	    return miscList;
	}
	return null;
    }

    public static void updateListStatus(String[] status) {
	if (!(status[0] == null)) {
	    eventStatus.setText(status[0]);
	}

	if (!(status[1] == null)) {
	    deadlineStatus.setText(status[1]);
	}

	if (!(status[2] == null)) {
	    miscStatus.setText(status[2]);
	}
    }

    public static String getListStatus(String listType) {
	String s = null;
	if (listType.equals("event")) {
	    s = eventStatus.getText();
	}
	if (listType.equals("deadline")) {
	    s = deadlineStatus.getText();
	}
	if (listType.equals("misc")) {
	    s = miscStatus.getText();
	}

	return s;
    }

    private String createTimeStamp() {
	Calendar cal = Calendar.getInstance();
	String DAY;
	String MONTH;
	String HOUR;
	String MIN;

	DAY = setDay(cal);
	MONTH = setMonth(cal);
	int year = cal.get(Calendar.YEAR);
	HOUR = setHour(cal);
	MIN = setMinutes(cal);

	String timeStamp = formatTimeToString(DAY, MONTH, HOUR, MIN, year);

	return timeStamp;
    }

    private String formatTimeToString(String DAY, String MONTH, String HOUR,
	    String MIN, int year) {
	String timeStamp = "[" + DAY + "/" +
		MONTH + "/" +
		year + " " +
		HOUR + ":" +
		MIN + "] ";
	
	return timeStamp;
    }

    private String setMinutes(Calendar cal) {
	String MIN;
	int minutes = cal.get(Calendar.MINUTE);
	if (minutes < 10) {
	    MIN = "0" + minutes;
	} else {
	    MIN = "" + minutes;
	}
	return MIN;
    }

    private String setHour(Calendar cal) {
	String HOUR;
	int hours = cal.get(Calendar.HOUR_OF_DAY);
	if (hours < 10) {
	    HOUR = "0" + hours;
	} else {
	    HOUR = "" + hours;
	}
	return HOUR;
    }

    private String setMonth(Calendar cal) {
	String MONTH;
	int month = cal.get(Calendar.MONTH) + 1;
	if (month < 10) {
	    MONTH = "0" + month;
	} else {
	    MONTH = "" + month;
	}
	return MONTH;
    }

    private String setDay(Calendar cal) {
	String DAY;
	int day = cal.get(Calendar.DAY_OF_MONTH);
	if (day < 10) {
	    DAY = "0" + day;
	} else {
	    DAY = "" + day;
	}
	return DAY;
    }
    
    /*
     * Algorithm for autoComplete works such that it continuously parses the text input of
     * user and looks up a possible matching word in the provided Dictionary (words). It is
     * done using Collection.binarySearch as it returns a negative number based on where the
     * text inputed by the user would be placed relative to the Dictionary. It then calculates
     * the index of the next possible match to the text input of the user and suggests it if the
     * text inputed by user is a partial match of the word in the Dictionary.
     */
    private void autoComplete() {
	IndexRange selected = textField.getSelection();
	String content = textField.getText().substring(0, selected.getStart()) + textField.getText().substring(selected.getEnd());

	int pos = textField.getCaretPosition() - 1;

	// Find where the word starts
	int w;
	for (w = pos; w >= 0; w--) {
	    try {
		if (!Character.isLetter(content.charAt(w))) {
		    break;
		}
	    } catch (Exception e) {
		break;
	    }
	}
	if (pos - w < 1) {
	    // Too few chars
	    return;
	}

	String prefix = content.substring(w + 1).toLowerCase();
	int n = Collections.binarySearch(words, prefix);

	ArrayList<String> possibleMatches = new ArrayList<String>();
	
	searchForPossibleMatch(prefix, n, possibleMatches);
	suggestToTextField(pos, w, prefix, possibleMatches);
    }

    private void searchForPossibleMatch(String prefix, int n,
	    ArrayList<String> possibleMatches) {
	if (n < 0 && -n <= words.size()) {
	    for (int i = -n -1; i < words.size(); i++) {
		String match = words.get(i);
		if (match.startsWith(prefix)) {
		    // A completion is found
		    possibleMatches.add(match);
		}
	    }
	} else if (n >= 0) {
	    for (int i = n - 1; i < words.size(); i++) {
		try {
		    String match = words.get(i);
		    if (match.startsWith(prefix)) {
			// A completion is found
			possibleMatches.add(match);
		    }
		} catch (Exception e) {
		    break;
		}
	    }
	}
    }

    private void suggestToTextField(int pos, int w, String prefix,
	    ArrayList<String> possibleMatches) {
	for (String match : possibleMatches) {
	    if (!match.equals(prefix) && match.length() > prefix.length()) {
		String completion = match.substring(pos - w);
		textField.insertText(pos + 1, completion);
		textField.selectRange(pos + 1, pos + 1 + completion.length());
		break;
	    }
	}
    }
}
